import { effect, isReactive, isRef, Ref } from "./reactive";
import { isArray, isFunction, isObject } from "./utils";

export function watchEffect(getter: () => void): () => void {
    return effect(getter).stop
}



type WatcherSource<T> = Ref<T> | (() => T)

type MapSources<T> = {
    [K in keyof T]: T[K] extends WatcherSource<infer V> ? V : never
}

interface WatchOptions {
    immediate?: boolean // default: false
    deep?: boolean
}

export function watch<T>(
    source: WatcherSource<T>,
    callback: (
        value: T,
        oldValue: T,
    ) => void,
): () => void

export function watch<T extends WatcherSource<unknown>[]>(
    sources: T,
    callback: (
        values: MapSources<T>,
        oldValues: MapSources<T>,
    ) => void,
): () => void

export function watch<T>(
    source: T,
    callback: (
        value: T | MapSources<T>,
        oldValue: T | MapSources<T>,
    ) => void,
): () => void {
    return doWatch(source, callback)
}

function doWatch(
    source,
    cb,
) {
    let deep = false
    let getter: () => any
    if (isArray(source)) {
        getter = () => {
            source.map(s => {
                if (isRef(s)) {
                    return s.value
                } else if (isReactive(s)) {
                    return traverse(s)
                } else if (isFunction(s)) {
                    return s()
                }
            })
        }
    } else if (isRef(source)) {
        getter = () => source.value
    } else if (isReactive(source)) {
        getter = () => source
        deep = true
    } else if (isFunction(source)) {
        getter = () => source
    } else {
        getter = () => { }
    }


    if (cb && deep) {
        const baseGetter = getter
        getter = () => traverse(baseGetter())
    }


    return effect(getter).stop
}


function traverse(value: unknown, seen: Set<unknown> = new Set()) {
    if (!isObject(value) || seen.has(value)) {
        return value
    }
    seen.add(value)
    if (isArray(value)) {
        for (let i = 0; i < value.length; i++) {
            traverse(value[i], seen)
        }
    } else if (value instanceof Map) {
        value.forEach((v, key) => {
            // to register mutation dep for existing keys
            traverse(value.get(key), seen)
        })
    } else if (value instanceof Set) {
        value.forEach(v => {
            traverse(v, seen)
        })
    } else {
        for (const key in value) {
            traverse(value[key], seen)
        }
    }
    return value
}
